#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
六度-周期性报告生成器 - 后端服务
使用 Flask 构建的报告生成系统
by: 六度科技 04826d.cn
"""

import os
import json
import random
import datetime
import re
import signal
import sys
from flask import Flask, request, jsonify, send_from_directory, render_template
from flask_cors import CORS
# 显式导入，以帮助 PyInstaller 打包
import flask_cors
from report_generator import markdown_to_docx

# 先创建基础应用，稍后会重新配置路径
app = Flask(__name__)
# 禁用静态文件缓存，实现前端文件的实时加载
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0
CORS(app)  # 允许所有来源的跨域请求

# --- 辅助函数：处理打包后的资源路径 ---
def resource_path(relative_path):
    """ 获取资源的绝对路径，无论是从源码运行还是从打包后的exe运行 """
    try:
        # PyInstaller 创建一个临时文件夹，并将路径存储在 _MEIPASS 中
        base_path = sys._MEIPASS
        # 打包后，文件在临时目录中
        return os.path.join(base_path, relative_path)
    except Exception:
        # 在非打包状态下，我们希望基础路径是当前脚本所在的目录
        base_path = os.path.dirname(os.path.abspath(__file__))
        return os.path.join(base_path, relative_path)

def get_app_root():
    """ 获取应用的根目录，打包后是exe所在目录，源码运行时是项目根目录 """
    try:
        # 如果是打包后的exe，数据目录应该在exe所在的目录
        if hasattr(sys, '_MEIPASS'):
            return os.path.dirname(os.path.realpath(sys.argv[0]))
        else:
            # 源码运行时，从server目录回到项目根目录
            return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    except:
        return os.path.dirname(os.path.realpath(sys.argv[0]))

def extract_resources():
    """在打包环境中自动释放资源文件到exe所在目录"""
    if not hasattr(sys, '_MEIPASS'):
        return  # 非打包环境，无需释放
    
    print("检测到打包环境，正在释放资源文件...")
    
    exe_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
    temp_dir = sys._MEIPASS
    
    # 需要释放的目录和文件
    resources_to_extract = [
        ('client', 'client'),
        ('templates', 'templates'),
        ('config.json', 'config.json')
    ]
    
    for temp_path, target_path in resources_to_extract:
        temp_full_path = os.path.join(temp_dir, temp_path)
        target_full_path = os.path.join(exe_dir, target_path)
        
        try:
            if os.path.isdir(temp_full_path):
                # 释放目录
                if not os.path.exists(target_full_path):
                    print(f"释放目录: {target_path}")
                    import shutil
                    shutil.copytree(temp_full_path, target_full_path)
                else:
                    print(f"目录已存在，跳过: {target_path}")
            elif os.path.isfile(temp_full_path):
                # 释放文件
                if not os.path.exists(target_full_path):
                    print(f"释放文件: {target_path}")
                    import shutil
                    os.makedirs(os.path.dirname(target_full_path), exist_ok=True)
                    shutil.copy2(temp_full_path, target_full_path)
                else:
                    print(f"文件已存在，跳过: {target_path}")
        except Exception as e:
            print(f"释放资源失败 {target_path}: {e}")
    
    print("资源文件释放完成")

def get_local_resource_path(relative_path):
    """获取exe所在目录的资源路径（打包后使用）"""
    exe_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
    return os.path.join(exe_dir, relative_path)

# --- 路径定义 (适配PyInstaller和源码运行) ---
APP_ROOT = get_app_root()

# 在打包环境中释放资源文件
extract_resources()

# 重新定义模板和静态文件夹路径
if hasattr(sys, '_MEIPASS'):
    # 打包后使用exe所在目录的资源
    app.template_folder = get_local_resource_path('client')
    app.static_folder = get_local_resource_path('client')
else:
    # 源码运行时使用相对路径
    app.template_folder = resource_path('client')
    app.static_folder = resource_path('client')

# 定义文件和文件夹路径
if hasattr(sys, '_MEIPASS'):
    # 打包后使用exe所在目录
    CONFIG_FILE = get_local_resource_path('config.json')
    TEMPLATES_DIR = get_local_resource_path('templates')
    MD_OUTPUT_DIR = get_local_resource_path('md')
    DOCX_OUTPUT_DIR = get_local_resource_path('docx')
else:
    # 源码运行时使用server目录
    server_dir = os.path.dirname(os.path.abspath(__file__))  # server目录
    CONFIG_FILE = os.path.join(server_dir, 'config.json')
    TEMPLATES_DIR = os.path.join(server_dir, 'templates')
    MD_OUTPUT_DIR = os.path.join(server_dir, 'md')
    DOCX_OUTPUT_DIR = os.path.join(server_dir, 'docx')

# 确保输出目录存在
os.makedirs(MD_OUTPUT_DIR, exist_ok=True)
os.makedirs(DOCX_OUTPUT_DIR, exist_ok=True)
os.makedirs(TEMPLATES_DIR, exist_ok=True) # 确保模板目录也存在

def load_config():
    """加载配置文件"""
    if os.path.exists(CONFIG_FILE):
        with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
            return json.load(f)
    return {}

def save_config(config):
    """保存配置文件"""
    with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
        json.dump(config, f, indent=2, ensure_ascii=False)

@app.route('/')
def serve_index():
    """提供主页"""
    try:
        # 打印调试信息
        print(f"Template folder: {app.template_folder}")
        print(f"Static folder: {app.static_folder}")
        print(f"APP_ROOT: {APP_ROOT}")
        
        # 检查模板文件是否存在
        template_path = os.path.join(app.template_folder, 'index.html')
        print(f"Looking for template at: {template_path}")
        print(f"Template exists: {os.path.exists(template_path)}")
        
        return render_template('index.html')
    except Exception as e:
        print(f"Error serving index: {e}")
        # 返回一个简单的错误页面
        return f"""
        <html>
        <head><title>六度-周期性报告生成器</title></head>
        <body>
        <h1>六度-周期性报告生成器</h1>
        <p>前端文件加载出错: {e}</p>
        <p>模板路径: {app.template_folder}</p>
        <p>静态文件路径: {app.static_folder}</p>
        <p>应用根目录: {APP_ROOT}</p>
        </body>
        </html>
        """, 500

@app.route('/api/config', methods=['GET', 'POST'])
def handle_config():
    """处理配置的读取和更新"""
    if request.method == 'GET':
        config = load_config()
        return jsonify(config)
    elif request.method == 'POST':
        data = request.json
        save_config(data)
        return jsonify({"status": 200, "message": "配置已更新"})

def _extract_events_from_monthly_md(period):
    """从月度报告MD文件中解析并提取事件表格"""
    year, month = period.split('-')
    md_filename = f"{year}年{month}月度报告.md"
    md_filepath = os.path.join(MD_OUTPUT_DIR, year, '月度报告', md_filename)

    if not os.path.exists(md_filepath):
        return [], f"未找到报告文件: {md_filepath}"

    with open(md_filepath, 'r', encoding='utf-8') as f:
        content = f.read()

    # 使用改进的正则表达式匹配整个表格
    # 从表头开始匹配到下一个分隔符（---）或章节标题（##）或文件结尾
    table_match = re.search(r'(\| 日期 \|.*?)(?=\n\n---|$|\n\n##)', content, re.DOTALL)
    
    if not table_match:
        return [], None # 文件中没有找到事件表格

    full_table = table_match.group(1).strip()
    if not full_table:
        return [], None # 表格为空

    # 提取数据行（跳过表头和分隔符行）
    lines = full_table.split('\n')
    data_lines = []
    for line in lines[2:]:  # 跳过表头和分隔符行
        if line.strip() and line.strip().startswith('|'):
            data_lines.append(line.strip())
    
    if not data_lines:
        return [], None # 没有数据行

    events = []
    for row in data_lines:
        columns = [col.strip() for col in row.strip().strip('|').split('|')]
        if len(columns) == 3: # 修改后的月度报告表格有3列
            event = {
                'date': columns[0],
                'type': columns[1],
                'description': columns[2],
            }
            events.append(event)
        elif len(columns) == 6: # 兼容旧版本的6列表格
            event = {
                'date': columns[0],
                'type': columns[1],
                'description': columns[2],
                'method': columns[3],
                'result': columns[4],
                'duration': columns[5],
            }
            events.append(event)
    
    return events, None

def generate_monthly_report_md(period, events):
    """生成月度报告的Markdown文本，自动填充总结"""
    try:
        # 使用辅助函数定位模板文件
        template_path = os.path.join(TEMPLATES_DIR, '月度维护报告模板.md')
        with open(template_path, 'r', encoding='utf-8') as f:
            template = f.read()
    except FileNotFoundError:
        return None, "月度报告模板文件不存在", []

    config = load_config()
    year, month = period.split('-')
    
    # --- 智能计算报告日期 ---
    # 获取月份的最后一天
    last_day_of_month = datetime.date(int(year), int(month) + 1, 1) - datetime.timedelta(days=1) if int(month) < 12 else datetime.date(int(year) + 1, 1, 1) - datetime.timedelta(days=1)
    # 在该月份的倒数1-3天内生成报告
    report_date = last_day_of_month - datetime.timedelta(days=random.randint(0, 2))
    
    # --- 自动生成总结内容 ---
    summary_template = config.get('monthly_summary_template', {})
    event_count = len(events)
    
    # 提取工作范围
    involved_scopes = list(set(event.get('type', '未知类型') for event in events))
    work_scopes_text = '、'.join(involved_scopes) if involved_scopes else '各项'
    
    # 模拟问题类型和改进建议
    issue_types_text = '、'.join(random.sample(involved_scopes, min(len(involved_scopes), 2))) if involved_scopes else "各类"
    suggested_areas_text = '、'.join(random.sample(involved_scopes, min(len(involved_scopes), 2))) if involved_scopes else "核心业务"

    # 填充总结模板
    summary_overview = summary_template.get('overview', '').format(work_scopes=work_scopes_text, event_count=event_count)
    summary_issues = summary_template.get('issues', '').format(issue_types=issue_types_text)
    summary_suggestions = summary_template.get('suggestions', '').format(suggested_areas=suggested_areas_text)
    
    # --- 构建替换字典 ---
    replacements = {
        "<year>": year,
        "<month>": month,
        "<report_date>": report_date.strftime('%Y年%m月%d日'),
        "<engineer_name>": config.get('engineer_name', ' '),
        "<client_name>": config.get('client_name', ' '),
        "<contractor_name>": config.get('contractor_name', ' '),
        "<summary_overview>": summary_overview,
        "<summary_issues>": summary_issues,
        "<summary_suggestions>": summary_suggestions
    }

    # 生成事件表格
    if events:
        table_header = "| 日期 | 类型 | 描述 |\n|---|---|---|\n"
        table_rows = []
        for event in events:
            row = f"| {event.get('date', ' ')} | {event.get('type', ' ')} | {event.get('description', ' ')} |"
            table_rows.append(row)
        replacements["<event_table>"] = table_header + "\n".join(table_rows)
    else:
        replacements["<event_table>"] = "本月无重大事件记录。"

    # 执行替换
    for placeholder, value in replacements.items():
        template = template.replace(placeholder, str(value))

    return template, None, []

def generate_quarterly_report_md(period, month_periods):
    """通过汇总月度报告生成季度报告的Markdown文本"""
    try:
        # 使用辅助函数定位模板文件
        template_path = os.path.join(TEMPLATES_DIR, '季度维护报告模板.md')
        with open(template_path, 'r', encoding='utf-8') as f:
            template = f.read()
    except FileNotFoundError:
        return None, "季度报告模板文件不存在", []

    # --- 汇总所有月度事件 ---
    all_events = []
    warnings = []
    for month_period in month_periods:
        monthly_events, error = _extract_events_from_monthly_md(month_period)
        if error:
            # 如果某个文件找不到，收集警告信息
            warnings.append(error)
            continue
        all_events.extend(monthly_events)

    config = load_config()
    year, quarter_num_str = period.split('-')
    quarter_num_str = quarter_num_str.replace('Q', '') # 确保只剩下数字
    q_map = {'1': '第一季度', '2': '第二季度', '3': '第三季度', '4': '第四季度'}
    q_text_map = {'1': '（1月至3月）', '2': '（4月至6月）', '3': '（7月至9月）', '4': '（10月至12月）'}
    
    quarter_display_text = f"{q_map.get(quarter_num_str, '')}{q_text_map.get(quarter_num_str, '')}"

    # --- 为汇总来的事件分配随机属性，用于计算和展示 ---
    event_options = config.get('event_generation_options', {})
    levels = event_options.get('levels', ['一般'])
    for event in all_events:
        if 'level' not in event: # 只有从月度报告来的事件没有级别
            event['level'] = random.choice(levels)
        if 'result' not in event:
            event['result'] = '已解决' # 假设月度事件都已解决

    # --- 智能计算KPI ---
    kpi_config = config.get('quarterly_kpi_config', {})
    total_events = len(all_events)
    availability = kpi_config.get('base_availability', 99.9)
    satisfaction = kpi_config.get('base_satisfaction', 98.0)
    
    if total_events > 0:
        # 计算解决率
        resolved_count = sum(1 for e in all_events if e.get('result') == '已解决')
        resolution_rate = (resolved_count / total_events) * 100
        
        # 根据事件严重性计算可用性
        severity_weights = kpi_config.get('severity_weights', {})
        for event in all_events:
            availability -= severity_weights.get(event.get('level'), 0)
        
        # 简单模拟满意度，可以根据未解决事件等调整
        satisfaction -= (total_events - resolved_count) * 2 # 每有一个未解决事件，满意度-2
    else:
        resolution_rate = 100.0

    # 确保 KPI 在合理范围内 (e.g., 0-100)
    availability = max(0, min(100, availability))
    satisfaction = max(0, min(100, satisfaction))
    resolution_rate = max(0, min(100, resolution_rate))

    # --- 智能计算报告日期 ---
    quarter_end_month = {'1': 3, '2': 6, '3': 9, '4': 12}
    end_month = quarter_end_month.get(quarter_num_str)
    # 获取季度最后一个月的最后一天
    last_day_of_quarter = datetime.date(int(year), end_month + 1, 1) - datetime.timedelta(days=1) if end_month < 12 else datetime.date(int(year) + 1, 1, 1) - datetime.timedelta(days=1)
    # 在该季度的倒数1-3天内生成报告
    report_date = last_day_of_quarter - datetime.timedelta(days=random.randint(0, 2))
    
    # --- 构建替换字典 ---
    replacements = {
        "<year>": year,
        "<quarter_text>": quarter_display_text,
        "<report_date>": report_date.strftime('%Y年%m月%d日'),
        "<engineer_name>": config.get('engineer_name', ' '),
        "<client_name>": config.get('client_name', ' '),
        "<contractor_name>": config.get('contractor_name', ' '),
        "<availability>": f"{availability:.2f}",
        "<resolution_rate>": f"{resolution_rate:.2f}",
        "<satisfaction>": f"{satisfaction:.2f}"
    }

    # 生成事件表格
    if all_events:
        table_header = "| 月份 | 日期 | 类型 | 故障级别 | 描述 | 影响范围 | 处理时长 | 根本原因 | 预防措施 |\n|---|---|---|---|---|---|---|---|---|\n"
        table_rows = []
        
        scopes = event_options.get('scopes', ['单个部门'])
        reasons = event_options.get('reasons', ['未知'])
        preventions = event_options.get('preventions', ['跟进'])

        for event in all_events:
            # 补齐用于展示的其他随机信息
            event['month'] = datetime.datetime.strptime(event.get('date'), '%Y-%m-%d').strftime('%m月') if event.get('date', '').count('-') == 2 else ' '
            event['scope'] = random.choice(scopes)
            event['reason'] = random.choice(reasons)
            event['prevention'] = random.choice(preventions)
            row = f"| {event.get('month', ' ')} | {event.get('date', ' ')} | {event.get('type', ' ')} | {event.get('level', ' ')} | {event.get('description', ' ')} | {event.get('scope', ' ')} | {event.get('duration', ' ')} | {event.get('reason', ' ')} | {event.get('prevention', ' ')} |"
            table_rows.append(row)
        replacements["<event_table>"] = table_header + "\n".join(table_rows)
    else:
        replacements["<event_table>"] = "本季度无重大事件记录或未找到相关月度报告。"

    # 执行替换
    for placeholder, value in replacements.items():
        template = template.replace(placeholder, str(value))

    return template, None, warnings

@app.route('/api/generate_report', methods=['POST'])
def generate_report():
    """
    API aTμеT: 生成月度或季度报告
    """
    data = request.json
    report_type = data.get('type')
    period = data.get('period')

    if not all([report_type, period]):
        return jsonify({"status": 400, "message": "缺少 'type' 或 'period' 参数"}), 400

    year, _ = period.split('-', 1)
    
    # 为年份创建子目录
    os.makedirs(os.path.join(MD_OUTPUT_DIR, year, '月度报告'), exist_ok=True)
    os.makedirs(os.path.join(MD_OUTPUT_DIR, year, '季度报告'), exist_ok=True)
    os.makedirs(os.path.join(DOCX_OUTPUT_DIR, year, '月度报告'), exist_ok=True)
    os.makedirs(os.path.join(DOCX_OUTPUT_DIR, year, '季度报告'), exist_ok=True)

    md_content = None
    md_filename = ""
    docx_filename = ""
    docx_subfolder = ""
    warnings = []

    if report_type == 'monthly':
        events = data.get('events', [])
        md_content, error, _ = generate_monthly_report_md(period, events)
        if error:
            return jsonify({"status": 500, "message": error}), 500
        
        _, month = period.split('-')
        md_filename = f"{year}年{month}月度报告.md"
        docx_filename = f"{year}年{month}月度报告.docx"
        docx_subfolder = os.path.join(year, '月度报告')

    elif report_type == 'quarterly':
        month_periods = data.get('month_periods')
        if not month_periods or len(month_periods) != 3:
            return jsonify({"status": 400, "message": "季度报告需要提供3个格式正确的月份周期"}), 400
        
        md_content, error, warnings = generate_quarterly_report_md(period, month_periods)
        if error:
            return jsonify({"status": 500, "message": error}), 500
            
        _, quarter_num_str = period.split('-')
        quarter_num_str = quarter_num_str.replace('Q', '') # 确保只剩下数字
        
        md_filename = f"{year}年第{quarter_num_str}季度报告.md"
        docx_filename = f"{year}年第{quarter_num_str}季度报告.docx"
        docx_subfolder = os.path.join(year, '季度报告')

    else:
        return jsonify({"status": 400, "message": "无效的报告类型"}), 400

    if md_content:
        # 构造完整的文件路径
        md_path = os.path.join(MD_OUTPUT_DIR, docx_subfolder, md_filename)
        docx_path = os.path.join(DOCX_OUTPUT_DIR, docx_subfolder, docx_filename)
        
        # 保存Markdown文件
        with open(md_path, 'w', encoding='utf-8') as f:
            f.write(md_content)
        
        # 转换Markdown到Word
        success, message = markdown_to_docx(md_path, docx_path)
        if success:
            response = {
                "status": 200, 
                "message": "报告生成成功"
            }
            if warnings:
                response["warnings"] = warnings
            return jsonify(response)
        else:
            response = {"status": 500, "message": f"Word文档转换失败: {message}"}
            if warnings:
                response["warnings"] = warnings
            return jsonify(response), 500
            
    response = {"status": 500, "message": "未知错误导致报告生成失败"}
    if warnings:
        response["warnings"] = warnings
    return jsonify(response), 500

@app.route('/api/list_files', methods=['GET'])
def list_files():
    """扫描并返回一个结构化的文件列表"""
    file_tree = {}
    base_dirs = {'md': MD_OUTPUT_DIR, 'docx': DOCX_OUTPUT_DIR}

    for dir_key, base_dir in base_dirs.items():
        file_tree[dir_key] = {}
        if not os.path.exists(base_dir):
            continue
        
        for root, dirs, files in os.walk(base_dir):
            # 只处理有文件的目录
            if not files:
                continue

            # 从根目录剥离路径，以构建层级结构
            relative_path = os.path.relpath(root, base_dir)
            
            # windows路径转为/
            relative_path = relative_path.replace('\\', '/')

            path_parts = relative_path.split('/')
            
            # 忽略根目录本身(relative_path为'.')
            if path_parts[0] == '.':
                continue

            # 确保层级结构存在
            current_level = file_tree[dir_key]
            for part in path_parts:
                current_level = current_level.setdefault(part, {})
            
            # 排序文件列表
            files.sort()
            # 将文件列表放入最内层
            # 找到正确的位置插入文件列表
            temp_level = file_tree[dir_key]
            for i, part in enumerate(path_parts):
                if i == len(path_parts) - 1:
                    temp_level[part] = files
                else:
                    temp_level = temp_level[part]
    
    return jsonify({
        "status": 200,
        "message": "文件列表获取成功",
        "files": file_tree
    })

if __name__ == '__main__':
    import time
    import threading
    import webbrowser
    
    def signal_handler(sig, frame):
        print('\n服务器正在关闭...')
        sys.exit(0)

    signal.signal(signal.SIGINT, signal_handler)
    
    print("="*60)
    print("六度-周期性报告生成器 - 后端服务")
    print("by: 六度科技 04826d.cn")
    print("="*60)
    
    # 打印调试信息
    print(f"APP_ROOT: {APP_ROOT}")
    print(f"Template folder: {app.template_folder}")
    print(f"Static folder: {app.static_folder}")
    print(f"Template exists: {os.path.exists(os.path.join(app.template_folder, 'index.html'))}")
    
    # 简化的启动流程（适合打包版本）
    is_packaged = hasattr(sys, '_MEIPASS')
    if is_packaged:
        print("检测到打包环境，直接启动服务...")
        url = "http://127.0.0.1:5000"
        print(f"访问地址: {url}")
        print("按 Ctrl+C 停止服务")
        print("="*60)
    else:
        # 倒计时功能（仅源码运行时）
        def countdown_with_option():
            """倒计时5秒，用户可选择是否自动打开浏览器"""
            url = "http://127.0.0.1:5000"
            print(f"服务器即将启动，访问地址: {url}")
            print("\n倒计时 5 秒后自动启动服务...")
            print("按 Enter 键立即启动服务并打开浏览器")
            print("或等待倒计时结束仅启动服务")
            print("-" * 60)
            
            user_input = []
            
            def input_listener():
                """监听用户输入"""
                try:
                    input()  # 等待用户按 Enter
                    user_input.append(True)
                except:
                    pass
            
            # 启动输入监听线程
            input_thread = threading.Thread(target=input_listener, daemon=True)
            input_thread.start()
            
            # 倒计时
            for i in range(5, 0, -1):
                if user_input:  # 用户按了 Enter
                    print(f"\n用户选择立即启动！正在打开浏览器...")
                    time.sleep(1)
                    try:
                        webbrowser.open(url)
                        print(f"浏览器已打开: {url}")
                    except Exception as e:
                        print(f"无法自动打开浏览器: {e}")
                        print(f"请手动在浏览器中访问: {url}")
                    break
                
                print(f"{i} 秒后启动服务...")
                time.sleep(1)
            else:
                # 倒计时结束，不打开浏览器
                print("\n仅启动服务，请手动打开浏览器")
            
            print("-" * 60)
            print("服务器启动成功！")
            print(f"访问地址: {url}")
            print("按 Ctrl+C 停止服务")
            print("="*60)
        
        # 执行倒计时
        countdown_with_option()
    
    # 启动 Flask 服务器
    try:
        # 根据是否打包决定是否启用调试模式
        debug_mode = not is_packaged  # 如果是打包状态，不启用调试
        app.run(host='0.0.0.0', port=5000, debug=debug_mode, use_reloader=False)
    except KeyboardInterrupt:
        print("\n服务器已停止")
    except Exception as e:
        print(f"\n服务器启动失败: {e}")
        if is_packaged:
            input("按 Enter 键退出...") 